package retry;

import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.util.ReflectUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.frxs.framework.util.common.ArrayUtil;
import com.frxs.framework.util.common.DateUtil;
import com.zwl.dropshipping.core.aspect.RetryableCompensationAspect;
import com.zwl.dropshipping.core.conf.apollo.ApolloConfigChangeEvent;
import com.zwl.dropshipping.core.enums.MqyyyyProductStatusEnum;
import com.zwl.dropshipping.core.mapstruct.EventRetryLogMapStruct;
import com.zwl.dropshipping.core.service.EventRetryCompensationService;
import com.zwl.dropshipping.core.service.EventRetryLogContentService;
import com.zwl.dropshipping.core.service.EventRetryLogService;
import com.zwl.dropshipping.dal.entity.EventRetryLogEntity;
import com.zwl.dropshipping.dal.model.EventRetryLogModel;
import com.zwl.dropshipping.jd.jos.wxwoork.JdWXWorkSendClient;
import com.zwl.yyyy.common.core.retry.annotation.Retryable;
import com.zwl.yyyy.common.kit.utils.SpringContextUtil;
import com.zwl.yyyy.common.kit.utils.busi.LogUtil;
import com.zwl.yyyy.common.util.CheckUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ClassUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;

import static com.zwl.dropshipping.core.aspect.RetryableCompensationAspect.paramSplitCharacter;
import static com.zwl.yyyy.common.core.AssertForException.ASSERT;
import static com.zwl.yyyy.common.core.AssertForException.ASSERT_BASE;

/**
 * @author zhangweilin
 * @date 2023/10/11 16:10
 * @Description:
 */
@Slf4j
@Service
public class EventRetryCompensationServiceImpl implements EventRetryCompensationService {


	@Autowired
	private EventRetryLogService eventRetryLogService;

	@Autowired
	private EventRetryLogContentService eventRetryLogContentService;

	@Autowired
	private ApolloConfigChangeEvent apolloConfigChangeEvent;

	@Autowired
	private TransactionTemplate transactionTemplate;

//	@Autowired
//	private JdWXWorkSendClient jdWXWorkSendClient;

	@Override
	public void compensation() {
		Integer eventRetryCompensationStartSecond = apolloConfigChangeEvent.getEventRetryCompensationStartSecond();
		Date now = new Date();
		Date start = DateUtil.addSeconds(now, -eventRetryCompensationStartSecond);
		List<EventRetryLogModel> eventRetryLogModelList = eventRetryLogService.queryNotFinishSelfLog(start);
		for (EventRetryLogModel eventRetryLogModel : eventRetryLogModelList) {
			String beanClassName = eventRetryLogModel.getBeanClassName();
			Object bean = SpringContextUtil.getBeanByClassName(beanClassName);
			String methodName = eventRetryLogModel.getMethodName();
			if (null == bean) {
				continue;
			}
			//===还原参数===
			//参数内容
			String contentStr = eventRetryLogModel.getContent();
			String paramClassNameStr = eventRetryLogModel.getParamClassName();

			//有参调用
			if (CheckUtil.isNotEmpty(paramClassNameStr) && CheckUtil.isNotEmpty(contentStr)) {
				String[] contentArr = contentStr.split(paramSplitCharacter);
				String[] paramClassNameArr = paramClassNameStr.split(paramSplitCharacter);
				ASSERT.isTrue(contentArr.length == paramClassNameArr.length, () -> "参数类型个数，与参数值个数不一致，请检查,eventRetryLogModel:" + eventRetryLogModel);

				List<Object> paramObjList = new ArrayList<>(paramClassNameArr.length);
				for (int i = 0; i < paramClassNameArr.length; i++) {
					String paramClassName = paramClassNameArr[i];
					try {
						//todo 暂时只考虑单体类型和List类型，没时间支持太多类型, 需要花时间专门针对各种类型组合单测，本期暂集中处理常见类型就够了
						Class<?> aClass = ClassUtils.getClass(paramClassName);
						String paramValue = contentArr[i];
						//如果是集合，入参请尽量不要用一些奇怪的集合，就统一List的子类最好
						//todo  有空再细测
						if (Collection.class.isAssignableFrom(aClass)) {
							List<?> objectList = JSONObject.parseArray(paramValue, aClass);
							paramObjList.add(objectList);
							//todo  有空再细测
						} else if (Map.class.isAssignableFrom(aClass)) {
							Map map = JSONObject.parseObject(paramValue, Map.class);
							paramObjList.add(map);
							//todo  有空再细测
						} else if (aClass.isArray()) {
							JSONArray jsonArray = JSONObject.parseArray(paramValue);
							Object[] objectArr = jsonArray.stream().toArray();
							paramObjList.add(objectArr);
							//如果就是简单实体对象,优先重点关注
						} else {
							//希望我们尽量都是单个入参，即都走此处，这样基本都能被还原出来
							Object object = JSONObject.parseObject(paramValue, aClass);
							paramObjList.add(object);
						}
					} catch (ClassNotFoundException e) {
						throw new IllegalArgumentException("参数类名找不到:" + paramClassName, e);
					}
				}

				Object[] argArr = paramObjList.toArray();
				Object target = com.zwl.yyyy.common.kit.utils.ReflectUtil.getOriginTarget(bean);
				Method method = ReflectUtil.getMethodOfObj(target, methodName, argArr);

				try {
					RetryableCompensationAspect.retryCallThreadLocal.set(true);
					ReflectUtil.invoke(bean, methodName, argArr);
					//处理已经成功了的
					handInvokeSuccess(eventRetryLogModel);
				} catch (Throwable e) {
					handInvokeException(eventRetryLogModel, method, argArr, getRealException(e));
				}
				//各阶段处理器
				doStepsHandler(eventRetryLogModel, argArr, method);
			} else {
				//todo 无参调用,有空再整
				RetryableCompensationAspect.retryCallThreadLocal.set(true);
				ReflectUtil.invoke(bean, methodName);
				eventRetryLogModel.setStatus(MqyyyyProductStatusEnum.SUCCESS.getValue());
			}
		}
	}

	/**
	 * 各阶段处理器
	 * @param eventRetryLogModel
	 * @param argArr
	 * @param method
	 */
	private void doStepsHandler(EventRetryLogModel eventRetryLogModel, Object[] argArr, Method method) {
		Retryable retryable = AnnotationUtils.getAnnotation(method, Retryable.class);
		if (null == retryable) {
			return;
		}
		String status = eventRetryLogModel.getStatus();
		//重试中处理器
		if (MqyyyyProductStatusEnum.INIT.getValue().equals(status)) {
			Class<? extends RetryResultHandler> retryResultHandler = retryable.initHandler();
			doRetryResultHandler(eventRetryLogModel, argArr, null, retryResultHandler);
			//重试成功处理器
		} else if (MqyyyyProductStatusEnum.SUCCESS.getValue().equals(status)) {
			Class<? extends RetryResultHandler> retryResultHandler = retryable.successHandler();
			doRetryResultHandler(eventRetryLogModel, argArr, null, retryResultHandler);
			//重试失败处理器
		} else if (MqyyyyProductStatusEnum.FAILURE.getValue().equals(status)) {
			Class<? extends RetryResultHandler> retryResultHandler = retryable.failureHandler();
			doRetryResultHandler(eventRetryLogModel, argArr, null, retryResultHandler);
		}
	}

	private void handInvokeSuccess(EventRetryLogModel eventRetryLogModel) {
		eventRetryLogModel.setStatus(MqyyyyProductStatusEnum.SUCCESS.getValue());
		EventRetryLogEntity eventRetryLogEntity = EventRetryLogMapStruct.INSTANCE.toEntity(eventRetryLogModel);
		transactionTemplate.execute(e -> {
			eventRetryLogService.updateById(eventRetryLogEntity);
			eventRetryLogContentService.deleteByBizCodeAndBizType(eventRetryLogModel.getBizCode(), eventRetryLogModel.getBizType());
			return null;
		});

	}


	private void handInvokeException(EventRetryLogModel eventRetryLogModel, Method method, Object[] argArr, Throwable e) {
		String beanClassName = eventRetryLogModel.getBeanClassName();
		String methodName = eventRetryLogModel.getMethodName();
		Integer retryTimes = increaseRetryTimes(eventRetryLogModel);
		LogUtil.error(log, e, "重试时失败了,调用为:{}.{}({})，详细参数:{}", beanClassName, methodName, ArrayUtil.toString(argArr), eventRetryLogModel);

		//如果达到重试次数
		if (retryTimes >= eventRetryLogModel.getMaxRetryTimes() && eventRetryLogModel.getMaxRetryTimes() > 0) {
			eventRetryLogModel.setStatus(MqyyyyProductStatusEnum.FAILURE.getValue());
		}
		EventRetryLogEntity eventRetryLogEntity = EventRetryLogMapStruct.INSTANCE.toEntity(eventRetryLogModel);
		eventRetryLogService.updateById(eventRetryLogEntity);

		//告警通知
		Retryable retryable = AnnotationUtils.getAnnotation(method, Retryable.class);
		if (null == retryable) {
			return;
		}

		Class<? extends RetryResultHandler> noticeHandlerClass = retryable.warnNoticeHandler();
		doRetryResultHandler(eventRetryLogModel, argArr, e, noticeHandlerClass);
	}

	private void doRetryResultHandler(EventRetryLogModel eventRetryLogModel, Object[] argArr, Throwable e,
			Class<? extends RetryResultHandler> noticeHandlerClass) {
		if (null == noticeHandlerClass) {
			return;
		}
		RetryResultHandler<Object> retryResultWarnNoticeHandler = SpringContextUtil.getBean(noticeHandlerClass);
		ASSERT_BASE.notNull(retryResultWarnNoticeHandler, noticeHandlerClass + ",没有实例，无法执行重试告警功能");
		try {
			retryResultWarnNoticeHandler.hand(eventRetryLogModel, argArr, e);
		} catch (Throwable ex) {
			LogUtil.error(log, ex, retryResultWarnNoticeHandler.getClass().getName() + " 内部发生异常");
		}
	}

	private Throwable getRealException(Throwable e) {
		if (!(e instanceof UtilException) && !(e instanceof InvocationTargetException)) {
			return e;
		}
		return getRealException(e.getCause());
	}

	private Integer increaseRetryTimes(EventRetryLogModel eventRetryLogModel) {
		//更新重试参数
		Integer retryTimes = eventRetryLogModel.getRetryTimes();
		retryTimes++;
		eventRetryLogModel.setRetryTimes(retryTimes);
		return retryTimes;
	}

}
